# -*- coding: utf-8 -*-
# @Time    : 2020/12/14 12:57
# @Author  : Guo_Zhanyu
# @File    : BoardBasics.py
import sys
import socket
import MainWindowNew
import Dialog
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow, QDialog, QApplication
from PyQt5.QtGui import QPalette, QColor, QPainter, QPen, QBrush
from functools import partial


# 棋盘窗口绘制各种参数
SIZE = 30         # 棋盘每个点的距离
LINES = 19        # 棋盘的行/列数
OUTER_WIDTH = 20  # 棋盘外宽度
INSIDE_WIDTH = 4  # 边框与棋盘最外的小间隔
BORDER_WIDTH = 4  # 边框宽度
LINE_WIDTH = 1    # 一般线宽
BORDER_LENGTH = SIZE*(LINES - 1) + INSIDE_WIDTH*2 + BORDER_WIDTH                    # 边框线长度
START_X = START_Y = OUTER_WIDTH + int(BORDER_WIDTH/2) + INSIDE_WIDTH                # 边框左上角坐标
SCREEN_HEIGHT = SIZE*(LINES - 1) + OUTER_WIDTH*2 + BORDER_WIDTH + INSIDE_WIDTH*2    # 屏幕高度
SCREEN_WIDTH = SCREEN_HEIGHT + 200                                                  # 游戏屏幕的宽

# 棋子绘制
STONE_R = SIZE//2 - 3   # 棋子显示半径
STONE_R2 = SIZE//2 + 3  # 执子提示半径

# 颜色
BOARD_COLOR = QColor(0xFA, 0xBE, 0x6E)      # 背景颜色
BLACK_COLOR = QColor(0, 0, 0)               # 黑色
WHITE_COLOR = QColor(255, 255, 255)         # 白色
RED_COLOR = QColor(200, 30, 30)             # 红色
BLUE_COLOR = QColor(30, 30, 200)            # 蓝色

# 棋子颜色
BLACK_STONE_COLOR = QColor(45, 45, 45)      # 黑子颜色
WHITE_STONE_COLOR = QColor(219, 219, 219)   # 白子颜色
YELLOW_STONE_COLOR = QColor(255, 215, 0)    # 黄子颜色

# 偏移量，用以遍历棋盘
offset = [(1, 0), (0, 1), (1, 1), (1, -1)]


class Chessman:
    """
    Define a Chessman's Properties.
    """
    def __init__(self, name, value, color):
        """
        Init a Chessman.

        :param name: Chessman's name -> str()
        :param value: Chessman's value -> int()
        :param color: Chessman's color -> QColor()
        """
        self.name = name
        self.value = value
        self.color = color
        pass
    pass


BLACK_CHESSMAN = Chessman('black', 1, BLACK_STONE_COLOR)
WHITE_CHESSMAN = Chessman('white', 2, WHITE_STONE_COLOR)
YELLOW_CHESSMAN = Chessman('yellow', 3, YELLOW_STONE_COLOR)


class Point:
    """
    Point(x, y) means a point on the board's frame.
    """
    def __init__(self, x=0, y=0):
        """
        Y is from Up to Down, X is from Left to Right.

        :param x: axis x value -> int()
        :param y: axis y value -> int()
        """
        self.x = x
        self.y = y
        pass
    pass


class Board:
    """
    Define a basic board.
    """
    def __init__(self, lines):
        """
        Init a board.

        :param lines: The dimension of the board.
        """
        self._lines = lines
        # 矩阵[LINESxLINES]，用来表示棋盘
        self._board = [[0] * self._lines for _ in range(self._lines)]
        self.cur_runner = BLACK_CHESSMAN  # 当前下棋者（CHESSMAN）
        self.winner = None                # 胜利者，没有则为空（CHESSMAN）
        self.last = None                  # 上一步（Point）
        self.history = []                 # 下子记录（[Point, Point, ...]）
        self.players = 2                  # 玩家数目
        pass

    def getBoard(self):
        """
        Get current board.

        :return: list()
        """
        return self._board

    def setBoard(self, new_board):
        """
        Set current board

        :param new_board: a new board to set -> list()
        :return: None
        """
        self._board = new_board
        pass

    def canDrop(self, point):
        """
        Determine if can drop at the specified point.

        :param point: a specified point -> Point()
        :return: bool()
        """
        return self._board[point.y][point.x] == 0

    def drop(self, chessman, point):
        """
        Drop a stone.

        :param chessman: who -> Chessman()
        :param point: point to drop -> Point()
        :return: the winner (if exists) -> Chessman()
        """
        self._board[point.y][point.x] = chessman.value
        self.last = point
        self.history.append(point)

        # 判断胜利
        if self.win(point):
            print(f'{chessman.name} win!')
            return chessman
        pass

    def win(self, point):
        """
        Determine if wins

        :param point: point to drop -> Point()
        :return: bool()
        """
        cur_value = self._board[point.y][point.x]
        for item in offset:
            if self.getCountOnDirection(point, cur_value, item[0], item[1]) >= 5:
                return True
            pass
        pass

    def getCountOnDirection(self, point, value, x_offset, y_offset):
        """
        Get the count of successive pieces on one direction.

        :param point: point to drop -> Point()
        :param value: stone's value -> int()
        :param x_offset: direction on x -> int()
        :param y_offset: direction on y -> int()
        :return: count -> int()
        """
        count = 1
        for step in range(1, 5):
            x = point.x + step*x_offset
            y = point.y + step*y_offset
            if 0 <= x < self._lines and 0 <= y < self._lines and self._board[y][x] == value:
                count += 1
            else:
                break
                pass
            pass

        for step in range(1, 5):
            x = point.x - step*x_offset
            y = point.y - step*y_offset
            if 0 <= x < self._lines and 0 <= y < self._lines and self._board[y][x] == value:
                count += 1
            else:
                break
                pass
            pass

        return count

    def getNextRunner(self, cur_runner):
        """
        Get next runner.

        :param cur_runner: current runner -> Chessman()
        :return: next runner -> Chessman()
        """
        if self.players == 2:
            if cur_runner == BLACK_CHESSMAN:
                return WHITE_CHESSMAN
            else:
                return BLACK_CHESSMAN
            pass
        elif self.players == 3:
            if cur_runner == BLACK_CHESSMAN:
                return WHITE_CHESSMAN
            elif cur_runner == WHITE_CHESSMAN:
                return YELLOW_CHESSMAN
            elif cur_runner == YELLOW_CHESSMAN:
                return BLACK_CHESSMAN
            pass
        pass

    pass


class BoardGUI(QMainWindow):
    """
    Board's GUI.
    """
    def __init__(self, lines):
        """
        Generate and Update a GUI, Connected with Server and Update timely.

        :param lines: The dimension of the board.
        """
        super(BoardGUI, self).__init__()
        self.MainWindowUI = MainWindowNew.Ui_MainWindow()
        self.MainWindowUI.setupUi(self)

        self.Dialog = QDialog()
        self.DialogUI = Dialog.Ui_Dialog()
        self.DialogUI.setupUi(self.Dialog)

        palette1 = QPalette()
        palette1.setColor(self.backgroundRole(), BOARD_COLOR)
        self.setPalette(palette1)

        self._lines = lines

        # 初始化自己的棋盘
        self.board = Board(lines)

        # 服务器地址与端口号（默认值），后续可以输入
        self.url = 'http://127.0.0.1:5000/'

        self.MainWindowUI.lineEdit_2.setText(self.getLocalIP())

        # 按钮的回调函数初始化
        self.MainWindowUI.pushButton.clicked.connect(partial(self.buttonFunc, self.MainWindowUI))
        self.MainWindowUI.pushButton_4.clicked.connect(self.regret)

        self.DialogUI.buttonBox.accepted.connect(self.rerun)
        self.DialogUI.buttonBox.rejected.connect(self.notrerun)
        pass

    def regret(self):
        """
        Callback of regret button.

        :return: None
        """
        # 如果有步可以悔棋
        if self.board.history:
            board = self.board.getBoard()
            board[self.board.last.y][self.board.last.x] = 0
            self.board.setBoard(board)
            self.board.cur_runner = self.board.getNextRunner(self.board.cur_runner)

            self.board.history.pop(-1)
            if self.board.history:
                self.board.last = self.board.history[-1]
            else:
                self.board.last = None
                pass

            self.update()
        else:
            print('Can not regret!')
            pass

        pass

    def rerun(self):
        """
        Callback of Dialog OK button

        :return: None
        """
        self.board = Board(self._lines)
        pass

    def notrerun(self):
        """
        Callback of Dialog Cancel button

        :return: None
        """
        # 使窗口不再弹出
        self.board.winner = None
        pass

    def paintEvent(self, event):
        """
        Update graphically

        :param event: paint event
        :return: None
        """
        qp = QPainter(self)
        qp.begin(self)
        # 抗锯齿
        qp.setRenderHint(QPainter.Antialiasing)
        self.drawBoard(qp)  # 画棋盘
        self.drawStones(qp)  # 画棋子

        if self.board.winner is not None:
            self.DialogUI.label.setText(f'{self.board.winner.name} 胜利，游戏结束，是否重新开始？')
            self.Dialog.show()
            pass

        qp.end()
        pass

    def mousePressEvent(self, event):
        """
        Mouse press function.

        :param event: mouse event
        :return: None
        """
        if event.button() == Qt.LeftButton:
            # 如果没有胜者
            if self.board.winner is None:
                mouse_pos = [event.pos().x(), event.pos().y()]
                # 得到鼠标的棋盘位置
                click_point = self.getClickPoint(mouse_pos)

                # 在棋盘范围内
                if click_point is not None:
                    if self.board.canDrop(click_point):
                        self.board.winner = self.board.drop(self.board.cur_runner, click_point)
                        self.board.cur_runner = self.board.getNextRunner(self.board.cur_runner)
                        pass
                    pass
                else:
                    print('超过棋盘范围...')
                    pass
                self.update()
                pass
            pass
        pass

    def getClickPoint(self, click_pos):
        """
        Get mouse click point on the board.

        :param click_pos: pixes location -> [int(), int()]
        :return: board axis location (if exists) -> Point()
        """
        pos_x = click_pos[0] - START_X
        pos_y = click_pos[1] - START_Y
        if pos_x < -INSIDE_WIDTH or pos_y < -INSIDE_WIDTH:
            return None
        x = pos_x // SIZE
        y = pos_y // SIZE
        if pos_x % SIZE > SIZE / 2:
            x += 1
            pass
        if pos_y % SIZE > SIZE / 2:
            y += 1
            pass
        if x >= LINES or y >= LINES:
            return None

        return Point(x, y)

    def drawStones(self, qp):
        """
        Draw exist stones.

        :param qp: handler -> QPainter()
        :return: None
        """
        # color = BLACK_STONE_COLOR if self.board.cur_runner == BLACK_CHESSMAN else WHITE_STONE_COLOR
        color = self.board.cur_runner.color
        # 初始化画笔
        pen = QPen(color, LINE_WIDTH, Qt.SolidLine)
        brush = QBrush(color)
        qp.setPen(pen)
        qp.setBrush(brush)
        # 画提示用的棋子
        qp.drawEllipse(650, 30, STONE_R2*2, STONE_R2*2)

        # 画上一步的方框
        if self.board.last is not None:
            pen.setColor(RED_COLOR)
            brush = QBrush(Qt.NoBrush)
            qp.setBrush(brush)
            qp.setPen(pen)
            qp.drawRect(START_X + SIZE * self.board.last.x - STONE_R, START_Y + SIZE * self.board.last.y - STONE_R,
                        STONE_R * 2, STONE_R * 2)
            pass

        # 每行每列遍历寻找存在的棋子并画出
        for i, row in enumerate(self.board.getBoard()):
            for j, cell in enumerate(row):
                if cell == BLACK_CHESSMAN.value:
                    self.drawStone(qp, Point(j, i), BLACK_CHESSMAN.color)
                elif cell == WHITE_CHESSMAN.value:
                    self.drawStone(qp, Point(j, i), WHITE_CHESSMAN.color)
                    pass
                elif cell == YELLOW_CHESSMAN.value:
                    self.drawStone(qp, Point(j, i), YELLOW_CHESSMAN.color)
                    pass
                pass
            pass
        pass

    def drawStone(self, qp, point, stone_color):
        """
        Draw a stone on a specified location.

        :param qp: handler -> QPainter()
        :param point: stone's location on the board -> Point()
        :param stone_color: stone's color -> QColor()
        :return: None
        """
        pen = QPen(stone_color, LINE_WIDTH, Qt.SolidLine)
        qp.setPen(pen)
        qp.setBrush(stone_color)
        qp.drawEllipse(START_X + SIZE * point.x - STONE_R, START_Y + SIZE * point.y - STONE_R,
                       STONE_R * 2, STONE_R * 2)
        pass

    def drawBoard(self, qp):
        """
        Draw the board.

        :param qp: handler -> QPainter()
        :return: None
        """
        pen = QPen(BLACK_COLOR, BORDER_WIDTH, Qt.SolidLine)
        qp.setPen(pen)
        qp.drawRect(OUTER_WIDTH, OUTER_WIDTH, BORDER_LENGTH, BORDER_LENGTH)
        self.drawLines(qp)
        self.drawBoardPoints(qp)
        pass

    def drawLines(self, qp):
        """
        Draw lines.

        :param qp: handler -> QPainter()
        :return: None
        """
        pen = QPen(BLACK_COLOR, LINE_WIDTH, Qt.SolidLine)
        qp.setPen(pen)
        for i in range(LINES):
            qp.drawLine(START_X, START_Y + SIZE*i, START_X + SIZE*(LINES - 1), START_Y + SIZE*i)
            pass

        for j in range(LINES):
            qp.drawLine(START_X + SIZE*j, START_Y, START_X + SIZE*j, START_Y + SIZE*(LINES - 1))
            pass
        pass

    def drawBoardPoints(self, qp):
        """
        Draw beautifully :)

        :param qp: handler -> QPainter()
        :return: None
        """
        qp.setBrush(BLACK_COLOR)
        for i in (3, 9, 15):
            for j in (3, 9, 15):
                if i == j == 9:
                    r = 5
                else:
                    r = 3
                    pass
                qp.drawEllipse(START_X + SIZE*i - r, START_Y + SIZE*j - r, r*2, r*2)
                pass
            pass
        pass

    def buttonFunc(self, ui):
        """
        Callback of MainWindow connect button

        :param ui: current ui -> MainWindow()
        :return: None
        """
        dataIn = ui.lineEdit.text()
        if dataIn != '':
            self.url = f'http://{dataIn}:5000/'
            pass
        ui.label_3.setText(self.url)
        pass

    def getLocalIP(self):
        """
        Get local IP.

        :return: ip -> str()
        """
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(('8.8.8.8', 80))
            ip = s.getsockname()[0]
        finally:
            s.close()
            pass
        return ip

    pass


if __name__ == '__main__':
    app = QApplication(sys.argv)
    board_ui = BoardGUI(LINES)
    board_ui.show()

    # 隐藏网络的控件
    board_ui.MainWindowUI.widget.hide()

    sys.exit(app.exec_())
    pass
